﻿/// <reference path="Core.ts" />

module Supernova {

    export class Channel {

        public online: boolean = false;

        public players: Array<Player> = [];

        private webSocket: WebSocket = null;

        public playerName: string = '';

        public playerCode: number = OwnerType.human;

        public nextCode = 0;

        encodePacket(parts: Array<Object>): string {
            var result = '';
            var first = true;
            for (var i in parts) {
                var part = parts[i].toString();
                var fixedPart = part.replace('\\', '\\\\').replace('"', '\"');
                if (part.indexOf(' ') != -1 || part == '') {
                    fixedPart = '"' + part + '"';
                }
                if (first) { first = false; } else { result += ' '; }
                result += fixedPart;
            }
            return result;
        }

        decodePacket(line: string): Array<string> {

            // A blank line contains no data at all
            if (line === '') return [];

            // Initialise some variables for the DFA-based state machine
            var result = [];
            var lexerState = 0;
            var currentPart = null;

            // Process every character in the line
            for (var i = 0; i < line.length; i++) {
                switch (lexerState) {
                    case 0:
                        // Lexer state 0 is the normal state
                        if (line[i] == ' ') {
                            result.push(currentPart);
                            currentPart = null;
                        }
                        else if (line[i] == '"') {
                            if (currentPart === null) currentPart = '';
                            lexerState = 1;
                        }
                        else {
                            if (currentPart === null) currentPart = '';
                            currentPart += line[i];
                        }
                        break;
                    case 1:
                        // Lexer state 1 is the quote-enclosed string state
                        if (line[i] == '"') { lexerState = 0; } else { currentPart += line[i]; }
                        break;
                }
            }

            // Return the parts (making sure to add the last part)
            result.push(currentPart);
            return result;

        }

        handleSocketOpen(e: Event) {
            console.log('[WebSocket:Open]');
            channel.online = true;
        }

        handleSocketClose(e: CloseEvent) {
            console.log('[WebSocket:Close] ' + e.code + ': "' + e.reason + '"');
            if (channel.online) {
                var message = 'The connection has been closed.';
                if (e.reason !== undefined && e.reason !== null && e.reason != '') {
                    message = e.reason;
                }
                dialogUI.showMessage(message, resetToMainMenu);
                channel.webSocket = null;
                channel.online = false;
            }
        }

        handleSocketError(e: Event) {
            console.log('[WebSocket:Error]');
            dialogUI.showMessage('The server is offline.', resetToMainMenu);
        }

        handleSocketMessage(e: MessageEvent) {
            if (Options.logIncomingPackets) {
                console.log('[WebSocket:Message] ' + e.data);
            }
            channel.execute(e.data);
        }

        public execute(line: string) {
            var parts = channel.decodePacket(line);
            var command = parts[0];
            var c = 1;
            if (command == 'PROTOCOL') {
                if (parts[1] == SERVER_PROTOCOL) {
                    channel.webSocket.send('HELLO');
                } else {
                    dialogUI.showMessage('Server uses protocol version ' + parts[1] + ' (client uses ' + SERVER_PROTOCOL + ').', function () { channel.webSocket.close(); });
                }
            } else if (command == 'STATE') {
                if (parts[1] == 'LOBBY') {
                    if (activeUI == serverSelectionUI) {
                        UI.swap(lobbyUI, UI.Bounds.create(canvas));
                    } else {
                        dialogUI.showMessage('The game has been completed.', resetToMainMenu);
                    }
                } else if (parts[1] == 'GAME_STARTING') {
                    UI.swap(null, UI.Bounds.create(canvas));
                } else if (parts[1] == 'GAME_IN_PROGRESS') {
                    newGame(GameMode.multiplayer);
                    galaxy.science = parseInt(parts[2]);
                    galaxy.gold = parseInt(parts[3]);
                    galaxy.profile.science.perExploredSystem = parseInt(parts[4]);
                    galaxy.profile.science.rate.noFacility = parseFloat(parts[5]);
                    galaxy.profile.science.rate.withFacility = parseFloat(parts[6]);
                }
                else if (parts[1] == 'REPLAY_SELECTION') {
                    UI.swap(replayUI, UI.Bounds.create(canvas));
                }
                else if (parts[1] == 'REPLAY_IN_PROGRESS') {
                    newGame(GameMode.replay);
                }
                else if (parts[1] == 'REPLAY_RATING') {
                    UI.swap(replayRatingUI, UI.Bounds.create(canvas));
                }
            } else if (command == 'CHAT') {
                Notices.addMessage(MessageType.chat, parts[1]);
            } else if (command == 'SERVER_BROADCAST') {
                Notices.addMessage(MessageType.fromServer, parts[1]);
            } else if (command == 'TERMINAL_ERROR') {
                dialogUI.showMessage(parts[1], function () { channel.webSocket.close(); });
            } else if (command == 'REPLAY_FILE') {
                for (var i in channel.observers.onReceiveReplayFileName) {
                    channel.observers.onReceiveReplayFileName[i](parts[1], parts[2]);
                }
            } else if (command == 'PLAYERS') {
                channel.players = [];
                for (var j = 1; j < parts.length; true) {
                    var player = new Player();
                    player.code = parseInt(parts[j]);
                    player.name = parts[j + 1];
                    player.state = parts[j + 2] == 'READY' ? 'ready' : 'lobby';
                    channel.players[player.code] = player;
                    j += 3;
                }
                for (var i in channel.observers.onPlayerListSupplied) {
                    channel.observers.onPlayerListSupplied[i](channel.players);
                }
            } else if (command == 'YOU') {
                channel.playerCode = parseInt(parts[1]);
                channel.nextCode = channel.playerCode * 10000;
                playerLookup[1] = playerLookup[OwnerType.computer - channel.playerCode];
                console.log('You have been assigned player code ' + channel.playerCode);
            } else if (command == 'JOINED') {
                var player = new Player();
                player.code = parseInt(parts[1]);
                player.name = parts[2];
                channel.players[player.code] = player;
                for (var i in channel.observers.onPlayerJoined) {
                    channel.observers.onPlayerJoined[i](player);
                }
            } else if (command == 'AVAILABLE_GENERATORS') {
                for (var i in channel.observers.onReceiveAvailableGenerators) {
                    channel.observers.onReceiveAvailableGenerators[i](parts.slice(1, parts.length));
                }
            } else if (command == 'AVAILABLE_GALAXY_SIZES') {
                for (var i in channel.observers.onReceiveAvailableGalaxySizes) {
                    channel.observers.onReceiveAvailableGalaxySizes[i](parts.slice(1, parts.length));
                }
            } else if (command == 'AVAILABLE_RESOURCES') {
                for (var i in channel.observers.onReceiveAvailableGalaxySizes) {
                    channel.observers.onReceiveAvailableResources[i](parts.slice(1, parts.length));
                }
            } else if (command == 'AVAILABLE_TRAITS') {
                var traitEntries: Array<{ description: string; points: number }> = [];
                while (c < parts.length) {
                    traitEntries.push({ description: parts[c++], points: parseInt(parts[c++]) });
                }
                for (var i in channel.observers.onReceiveAvailableTraits) {
                    channel.observers.onReceiveAvailableTraits[i](traitEntries);
                }
            } else if (command == 'CONFIGURE_MULTIPLAYER') {
                var generator = parseInt(parts[1]);
                var galaxySize = parseInt(parts[2]);
                var resources = parseInt(parts[3]);
                var traits = parseInt(parts[4]);
                var goodies = parseInt(parts[5]) == 1;
                var wrecks = parseInt(parts[6]) == 1;
                for (var i in channel.observers.onChangeMultiplayerConfig) {
                    channel.observers.onChangeMultiplayerConfig[i](generator, galaxySize, resources, traits, goodies, wrecks);
                }
            } else if (command == 'DISCONNECTED') {

                // Tell player who disconnected
                var playerCode = parseInt(parts[1]);
                var player = channel.players[playerCode];
                Notices.addMessage(MessageType.fromServer, player.name + ' has disconnected.');

                // Notify observers
                delete channel.players[player.code];
                for (var i in channel.observers.onPlayerDisconnected) {
                    channel.observers.onPlayerDisconnected[i](player);
                }

                // Remove star systems
                if (galaxy !== null) {
                    var ownerCode = playerCode == channel.playerCode ? OwnerType.human : OwnerType.computer - playerCode;
                    for (var j = 0; j < galaxy.starSystems.length; j++) {
                        var system = galaxy.starSystems[j];
                        if (system.owner == ownerCode) {
                            system.depopulate();
                        }
                    }

                    // Remove ships
                    var ships = findShips(function (s) { return s.owner == ownerCode });
                    for (var j = 0; j < ships.length; j++) {
                        ships[j].die(true);
                    }
                }
            } else if (command == 'READY') {
                var playerCode = parseInt(parts[1]);
                var player = channel.players[playerCode];
                player.state = 'ready';
                for (var i in channel.observers.onPlayerReady) {
                    channel.observers.onPlayerReady[i](player);
                }
            } else if (command == 'NOT_READY') {
                var playerCode = parseInt(parts[1]);
                var player = channel.players[playerCode];
                player.state = 'lobby';
                for (var i in channel.observers.onPlayerNotReady) {
                    channel.observers.onPlayerNotReady[i](player);
                }
            } else if (command == 'SYSTEM') {
                var system = new StarSystem();
                var ownerCode = parseInt(parts[5]);
                system.code = parseInt(parts[1]);
                system.name = parts[2];
                system.x = parseFloat(parts[3]);
                system.y = parseFloat(parts[4]);
                if (ownerCode == channel.playerCode) {
                    system.owner = OwnerType.human;
                } else if (ownerCode == -1) {
                    system.owner = OwnerType.none;
                } else {
                    system.owner = OwnerType.computer - ownerCode;
                }
                system.discovered = ownerCode == channel.playerCode;
                galaxy.starSystems.push(system);
            } else if (command == 'BODY') {
                var systemCode = parseInt(parts[1]);
                var bodyType = parseInt(parts[2]);
                var system = findSystem(function (system) { return system.code == systemCode });
                var body = new Body(system, bodyType);
                body.distance = parseFloat(parts[3]);
                body.initialAngle = parseFloat(parts[4]);
                body.angle = body.initialAngle;
                body.population = parseInt(parts[5]);
                body.maxPopulation = parseInt(parts[6]);
                body.gold = parseInt(parts[7]);
                body.goodies.gold = parseInt(parts[8]);
                body.goodies.science = parseInt(parts[9]);
                body.code = system.bodies.push(body) - 1;
            } else if (command == 'BODY_FACILITY') {
                var systemCode = parseInt(parts[1]);
                var bodyCode = parseInt(parts[2]);
                var facilityName = parts[3];
                var system = findSystem(function (system) { return system.code == systemCode });
                var body = system.bodies[bodyCode];
                if (facilityName == 'SHIPYARD') body.facilities.shipyard = true;
                if (facilityName == 'COMMS') body.facilities.comms = true;
                if (facilityName == 'SCIENCE') body.facilities.science = true;
                if (facilityName == 'ORBITAL') body.facilities.orbitals = parseInt(parts[4]);
                if (facilityName == 'MINE') body.facilities.mine = true;
                if (facilityName == 'SCANNER') body.facilities.scanner = true;
                if (facilityName == 'ARTILLERY') body.facilities.artillery = new BodyFacilityArtillery();
            } else if (command == 'WRECK') {
                var wreck = new Wreck();
                wreck.code = parseInt(parts[c++]);
                wreck.x = parseFloat(parts[c++]);
                wreck.y = parseFloat(parts[c++]);
                wreck.quantity = parseInt(parts[c++]);
                galaxy.wrecks[wreck.code] = wreck;
            } else if (command == 'SLOT_DATA') {
                var arrayName = parts[1];
                var values = [];
                if (arrayName == 'COLONY') values = config.slotData.colony;
                if (arrayName == 'SCANNER') values = config.slotData.scanner;
                if (arrayName == 'SPEEDS') values = config.slotData.speeds;
                if (arrayName == 'MINING_RATE') values = config.slotData.miningRate;
                if (arrayName == 'MINING_AMOUNT') values = config.slotData.miningAmount;
                if (arrayName == 'ARMOR') values = config.slotData.armor;
                if (arrayName == 'SHIELDS') values = config.slotData.shields;
                if (arrayName == 'ATTACK_POWER') values = config.slotData.attackPower;
                if (arrayName == 'ATTACK_RADIUS') values = config.slotData.attackRadius;
                if (arrayName == 'ATTACK_DELAY') values = config.slotData.attackDelay;
                if (arrayName == 'BOMBARD_POWER') values = config.slotData.bombardPower;
                if (arrayName == 'BOMBARD_DELAY') values = config.slotData.bombardDelay;
                var newValues = parts.slice(2);
                for (var j = 0; j < Math.min(values.length, newValues.length); j++) {
                    values[j] = parseFloat(newValues[j]);
                }
                for (var j = values.length; j < newValues.length; j++) {
                    values.push(parseFloat(newValues[j]));
                }
                for (var j = newValues.length; j < values.length; j++) {
                    values.pop();
                }
            } else if (command == 'BEGIN') {
                newGameReady();
            } else if (command == 'CREATE_SHIPS') {

                // Decode packet
                var ownerCode = parseInt(parts[c++]);
                var count = parseInt(parts[c++]);
                var systemCode = parseInt(parts[c++]);
                var bodyCode = parseInt(parts[c++]);
                var firstShipCode = parseInt(parts[c++]);
                var presetPopulation = parseInt(parts[c++]);
                var presetTerraform = parseInt(parts[c++]);
                var presetScanning = parseInt(parts[c++]);
                var presetSpeed = parseInt(parts[c++]);
                var presetArmor = parseInt(parts[c++]);
                var presetShields = parseInt(parts[c++]);
                var presetAttack = parseInt(parts[c++]);
                var presetBombard = parseInt(parts[c++]);
                var presetMining = parseInt(parts[c++]);
                var pods = parseInt(parts[c++]);
                var name = parts[c++];
                var cost = parseInt(parts[c++]);

                // Find entities
                var system = findSystemByCode(systemCode);
                var body = system.bodies[bodyCode];
                var owner = ownerCode == channel.playerCode ? OwnerType.human : OwnerType.computer - ownerCode; // Negative owners mean "enemy"

                // Create ships
                for (var shipCode = firstShipCode; shipCode < firstShipCode + count; shipCode++) {
                    var preset = new ShipPreset();
                    preset.population = presetPopulation;
                    preset.terraform = presetTerraform;
                    preset.scanning = presetScanning;
                    preset.speed = presetSpeed;
                    preset.armor = presetArmor;
                    preset.shields = presetShields;
                    preset.attack = presetAttack;
                    preset.bombard = presetBombard;
                    preset.mining = presetMining;
                    var newShip = Ship.create(body, preset, pods, owner, name);
                    newShip.code = shipCode;
                    newShip.cost = cost;
                }
                if (galaxy.mode == GameMode.replay) Sounds.play(Sounds.items.blip);

            } else if (command == 'UPDATE_PRESET') {
                var shipCode = parseInt(parts[1]);
                var ship = findShip(function (s) { return s.code == shipCode });
                ship.preset.population = parseInt(parts[2]);
                ship.preset.terraform = parseInt(parts[3]);
                ship.preset.scanning = parseInt(parts[4]);
                ship.preset.speed = parseInt(parts[5]);
                ship.preset.armor = parseInt(parts[6]);
                ship.preset.shields = parseInt(parts[7]);
                ship.preset.attack = parseInt(parts[8]);
                ship.preset.bombard = parseInt(parts[9]);
                ship.preset.mining = parseInt(parts[10]);
                ship.updateStats();
            } else if (command == 'MOVE_SHIPS') {
                var j = 1;
                while (j < parts.length) {
                    var sourceShipCode = parseInt(parts[j]);
                    var targetSystemCode = parseInt(parts[j + 1]);
                    var targetBodyCode = parseInt(parts[j + 2]);
                    var targetShipCode = parseInt(parts[j + 3]);
                    var targetX = parseFloat(parts[j + 4]);
                    var targetY = parseFloat(parts[j + 5]);
                    j += 6;
                    var order = ShipOrder.create(ShipOrderType.move);
                    order.targetLocation = new Location();
                    if (targetSystemCode != -1 && targetBodyCode != -1) {
                        var system = findSystem(function (system) { return system.code == targetSystemCode });
                        var body = system.bodies[targetBodyCode];
                        order.targetLocation.setBody(body);
                    } else if (targetSystemCode != -1) {
                        var system = findSystem(function (system) { return system.code == targetSystemCode });
                        order.targetLocation.setSystem(system);
                    } else if (targetShipCode != -1) {
                        var targetShip = findShip(function (s) { return s.code == targetShipCode });
                        order.targetLocation.setShip(targetShip);
                    } else {
                        order.targetLocation.setXY(targetX, targetY);
                    }
                    var ship = findShip(function (s) { return s.code == sourceShipCode });
                    ship.setSingleOrder(order);
                }
            } else if (command == 'TERRAFORM') {
                var shipCode = parseInt(parts[1]);
                var systemCode = parseInt(parts[2]);
                var bodyCode = parseInt(parts[3]);
                var resourcesUsed = parseFloat(parts[4]);
                var ship = findShip(function (s) { return s.code == shipCode });
                var system = findSystem(function (system) { return system.code == systemCode });
                var body = system.bodies[bodyCode];
                ship.terraformAction(body, resourcesUsed);
            } else if (command == 'COLONISE') {
                var shipCode = parseInt(parts[1]);
                var systemCode = parseInt(parts[2]);
                var bodyCode = parseInt(parts[3]);
                var ship = findShip(function (s) { return s.code == shipCode });
                var system = findSystem(function (system) { return system.code == systemCode });
                var body = system.bodies[bodyCode];
                ship.coloniseAction(body);
            } else if (command == 'BOMBARD') {
                var shipCode = parseInt(parts[c++]);
                var systemCode = parseInt(parts[c++]);
                var bodyCode = parseInt(parts[c++]);
                var preBombardPopulation = parseFloat(parts[c++]);
                var ship = findShip(function (s) { return s.code == shipCode });
                var body = findSystemBodyByCode(systemCode, bodyCode);
                body.population = preBombardPopulation;
                ship.bombardAction(body);
            } else if (command == 'STOP') {
                var shipCode = parseInt(parts[1]);
                var targetX = parseFloat(parts[2]);
                var targetY = parseFloat(parts[3]);
                var ship = findShip(function (s) { return s.code == shipCode });
                var order = ShipOrder.createWithLocation(ShipOrderType.move, targetX, targetY);
                ship.setSingleOrder(order);
            } else if (command == 'REPAIR') {
                var shipCode = parseInt(parts[1]);
                var initialArmor = parseFloat(parts[2]);
                var ship = findShip(function (s) { return s.code == shipCode });
                ship.armor = initialArmor;
                ship.repairAction();
            } else if (command == 'DISBAND') {
                var shipCode = parseInt(parts[1]);
                var ship = findShip(function (s) { return s.code == shipCode });
                ship.disbandAction();
            } else if (command == 'DESTROY') {
                var shipCode = parseInt(parts[1]);
                var ship = findShip(function (s) { return s.code == shipCode });
                ship.die(true);
            } else if (command == 'REMOVE_GOODIES') {
                var systemCode = parseInt(parts[1]);
                var system = findSystem(function (system) { return system.code == systemCode });
                var bodyCode = parseInt(parts[2]);
                var body = system.bodies[bodyCode];
                if (galaxy.mode == GameMode.replay) {
                    if (body.goodies.gold > 0) Sounds.play(Sounds.items.collectGold);
                    if (body.goodies.science > 0) Sounds.play(Sounds.items.collectScience);
                }
                body.goodies.gold = 0;
                body.goodies.science = 0;
            } else if (command == 'REMOVE_WRECK') {
                var wreckCode = parseInt(parts[1]);
                delete galaxy.wrecks[wreckCode];
                if (galaxy.mode == GameMode.replay) Sounds.play(Sounds.items.collectWreck);
            } else if (command == 'BODY_TYPE') {
                var body = findSystemBodyByCode(parseInt(parts[1]), parseInt(parts[2]));
                body.type = parseInt(parts[3]);
            } else if (command == 'BODY_POPULATION') {
                var body = findSystemBodyByCode(parseInt(parts[1]), parseInt(parts[2]));
                body.population = parseFloat(parts[3]);
            } else if (command == 'BODY_MAX_POPULATION') {
                var body = findSystemBodyByCode(parseInt(parts[1]), parseInt(parts[2]));
                body.maxPopulation = parseInt(parts[3]);
            } else if (command == 'ARTILLERY') {
                var type = parseInt(parts[c++]);
                if (type == ArtilleryType.bodyToBody) {
                    var sourceSystem = findSystemByCode(parseInt(parts[c++]));
                    var sourceBody = sourceSystem.bodies[parseInt(parts[c++])];
                    var targetSystem = findSystemByCode(parseInt(parts[c++]));
                    var targetBody = targetSystem.bodies[parseInt(parts[c++])];
                    var strength = parseInt(parts[c++]);
                    var ignoreOrbitals = parseInt(parts[c++]) == 1;
                    sourceBody.facilities.artillery.launch(sourceBody, targetBody, strength, ignoreOrbitals);
                } else if (type == ArtilleryType.shipToBody) {
                    var ship = findShipByCode(parseInt(parts[c++]));
                    var targetSystem = findSystemByCode(parseInt(parts[c++]));
                    var targetBody = targetSystem.bodies[parseInt(parts[c++])];
                    var strength = parseInt(parts[c++]);
                    var ignoreOrbitals = parseInt(parts[c++]) == 1;
                    ship.launchArtillery(targetBody, strength, ignoreOrbitals);
                }
            }
        }

        public connect(address: string, protocol: string) {
            this.webSocket = new WebSocket(address, protocol);
            this.webSocket.onopen = this.handleSocketOpen;
            this.webSocket.onclose = this.handleSocketClose;
            this.webSocket.onmessage = this.handleSocketMessage;
            this.webSocket.onerror = this.handleSocketError;
        }

        public disconnect() {
            if (this.webSocket !== null) {
                this.webSocket.close();
                this.webSocket = null;
                this.online = false;
            }
        }

        public observers: {
            onPlayerJoined: Array<(player: Player) => void>;
            onPlayerDisconnected: Array<(player: Player) => void>;
            onPlayerListSupplied: Array<(players: Array<Player>) => void>;
            onReceiveAvailableGenerators: Array<(description: Array<string>) => void>,
            onReceiveAvailableGalaxySizes: Array<(descriptions: Array<string>) => void>;
            onReceiveAvailableResources: Array<(descriptions: Array<string>) => void>;
            onReceiveAvailableTraits: Array<(traits: Array<{ description: string; points: number }>) => void>;
            onChangeMultiplayerConfig: Array<(generator: number, galaxySize: number, resources: number, traits: number, goodies: boolean, wrecks: boolean) => void>;
            onPlayerReady: Array<(player: Player) => void>;
            onPlayerNotReady: Array<(player: Player) => void>;
            onReceiveReplayFileName: Array<(title: string, fileName: string) => void>
        } = {
            onPlayerJoined: [],
            onPlayerDisconnected: [],
            onPlayerListSupplied: [],
            onReceiveAvailableGenerators: [],
            onReceiveAvailableGalaxySizes: [],
            onReceiveAvailableResources: [],
            onReceiveAvailableTraits: [],
            onChangeMultiplayerConfig: [],
            onPlayerReady: [],
            onPlayerNotReady: [],
            onReceiveReplayFileName: []
        };

        public subscribe(eventName: string, f: Function) {
            this.observers[eventName].push(f);
        }

        public unsubscribe(eventName: string, f: Function) {
            var observerList = this.observers[eventName];
            observerList.splice(observerList.indexOf(f), 1);
        }

        public invoke = {

            joinGame: function (name: string) {
                channel.playerName = name;
                channel.webSocket.send(channel.encodePacket(['JOIN', name]));
            },
            configureMultiplayer: function (generator: number, galaxySize: number, resources: number, traits: number, goodies: boolean, wrecks: boolean) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['CONFIGURE_MULTIPLAYER', generator, galaxySize, resources, traits, goodies ? 1 : 0, wrecks ? 1 : 0]));
            },
            ready: function () {
                channel.webSocket.send('READY');
            },
            notReady: function () {
                channel.webSocket.send('NOT_READY');
            },
            createPresetShips: function (count: number, systemCode: number, bodyCode: number, shipCode: number, preset: ShipPreset, podCount: number, name: string, cost: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['CREATE_SHIPS', count, systemCode, bodyCode, shipCode, preset.population, preset.terraform, preset.scanning, preset.speed, preset.armor, preset.shields, preset.attack, preset.bombard, preset.mining, podCount, name, cost]));
            },
            updateShipPreset: function (shipCode: number, preset: ShipPreset) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['UPDATE_PRESET', shipCode, preset.population, preset.terraform, preset.scanning, preset.speed, preset.armor, preset.shields, preset.attack, preset.bombard, preset.mining]));
            },
            moveShip: function (sourceShipCode: number, targetSystemCode: number, targetBodyCode: number, targetShipCode: number, targetX: number, targetY: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['MOVE_SHIPS', sourceShipCode, targetSystemCode, targetBodyCode, targetShipCode, targetX, targetY]));
            },
            moveShips: function (shipsToMove) {
                var message = ['MOVE_SHIPS'];
                for (var entryKey in shipsToMove) {
                    message = message.concat(shipsToMove[entryKey]);
                }
                if (channel.online) channel.webSocket.send(channel.encodePacket(message));
            },
            terraform: function (shipCode: number, targetSystemCode: number, targetBodyCode: number, resourcesUsed: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['TERRAFORM', shipCode, targetSystemCode, targetBodyCode, resourcesUsed]));
            },
            colonise: function (shipCode: number, targetSystemCode: number, targetBodyCode: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['COLONISE', shipCode, targetSystemCode, targetBodyCode]));
            },
            artilleryBodyBody: function (systemCode: number, bodyCode: number, targetSystemCode: number, targetBodyCode: number, strength: number, ignoreOrbitals: boolean) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['ARTILLERY', ArtilleryType.bodyToBody, systemCode, bodyCode, targetSystemCode, targetBodyCode, strength, ignoreOrbitals ? 1 : 0]));
            },
            artilleryShipBody: function (shipCode: number, targetSystemCode: number, targetBodyCode: number, strength: number, ignoreOrbitals: boolean) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['ARTILLERY', ArtilleryType.shipToBody, shipCode, targetSystemCode, targetBodyCode, strength, ignoreOrbitals ? 1 : 0]));
            },
            bombard: function (shipCode: number, targetSystemCode: number, targetBodyCode: number, preBombardPopulation: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['BOMBARD', shipCode, targetSystemCode, targetBodyCode, preBombardPopulation]));
            },
            stop: function (shipCode: number, targetX: number, targetY: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['STOP', shipCode, targetX, targetY]));
            },
            repair: function (shipCode: number, initialArmor: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['REPAIR', shipCode, initialArmor]));
            },
            disband: function (shipCode: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['DISBAND', shipCode]));
            },
            destroy: function (shipCode: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['DESTROY', shipCode]));
            },
            removeGoodies: function (systemCode: number, bodyCode: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['REMOVE_GOODIES', systemCode, bodyCode]));
            },
            removeWreck: function (wreckCode: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['REMOVE_WRECK', wreckCode]));
            },
            updateBodyType: function (systemCode: number, bodyCode: number, bodyType: BodyType) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['BODY_TYPE', systemCode, bodyCode, bodyType]));
            },
            updateBodyPopulation: function (systemCode: number, bodyCode: number, population: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['BODY_POPULATION', systemCode, bodyCode, population]));
            },
            updateBodyMaxPopulation: function (systemCode: number, bodyCode: number, maxPopulation: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['BODY_MAX_POPULATION', systemCode, bodyCode, maxPopulation]));
            },
            updateBodyFacility: function (systemCode: number, bodyCode: number, facilityName: string, facilityValue: number = 0) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['BODY_FACILITY', systemCode, bodyCode, facilityName.toUpperCase(), facilityValue]));
            },
            enterReplaySelection: function () {
                if (channel.online) channel.webSocket.send('ENTER_REPLAY_SELECTION');
            },
            getReplays: function (count: number, sort: string) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['GET_REPLAYS', count, sort]));
            },
            startReplay: function (fileName: string) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['START_REPLAY', fileName]));
            },
            rateReplay: function (rating: number) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['RATE_REPLAY', rating]));
            },
            admin: function (command) {
                if (channel.online) channel.webSocket.send('ADMIN ' + command);
            },
            serverCommand: function (command) {
                if (channel.online) channel.webSocket.send('SERVER ' + command);
            },
            chat: function (message: string) {
                if (channel.online) channel.webSocket.send(channel.encodePacket(['CHAT', message]));
            }
        };

    };

}